package com.atguigu.tingshu.payment.service.impl;

import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.model.payment.PaymentInfo;
import com.atguigu.tingshu.payment.config.WxPayV3Config;
import com.atguigu.tingshu.payment.service.PaymentInfoService;
import com.atguigu.tingshu.payment.service.WxPayService;
import com.atguigu.tingshu.payment.util.PayUtil;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.swing.plaf.ProgressBarUI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private PaymentInfoService paymentInfoService;

    @Autowired
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;

    @Autowired
    private WxPayV3Config wxPayV3Config;

    /**
     * 选择微信支付：1.保存本地交易记录  2.集成微信SDK获取小程序拉起微信支付所需参数
     *
     * @param paymentType 支付类型：1301=订单  1302=充值
     * @param orderNo     订单编号
     * @return {timeStamp:"时间戳",nonceStr:"随机字符串",package:"统一下单接口返回的 prepay_id", paySign:"签名", signType:"签名算法"}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> getMiniProgramePayParams(String paymentType, String orderNo) {
        try {
            //1.保存本地交易记录
            PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(paymentType, orderNo);
            if (SystemConstant.PAYMENT_STATUS_PAID.equals(paymentInfo.getPaymentStatus())) {
                throw new GuiguException(500, "请勿重复支付");
            }

            //2.对接微信支付获取小程序拉起微信支付所需参数
            //2.1 调用微信支付接口业务对象
            JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
            //2.2 构建请求参数
            PrepayRequest request = new PrepayRequest();
            Amount amount = new Amount();
            //TODO开发阶段 默认应付金额：1分
            amount.setTotal(1);
            request.setAmount(amount);
            request.setAppid(wxPayV3Config.getAppid());
            request.setMchid(wxPayV3Config.getMerchantId());
            request.setDescription("谷粒随享**商品");
            request.setNotifyUrl(wxPayV3Config.getNotifyUrl());
            //商户侧订单编号
            request.setOutTradeNo(paymentInfo.getOrderNo());
            //未上线小程序还需配置付款人微信账户ID-要求必须是该小程序应用下开发者账户权限才能支付
            Payer payer = new Payer();
            payer.setOpenid("odo3j4qp-wC3HVq9Z_D9C0cOr0Zs");
            request.setPayer(payer);
            //2.3 调用下单并生成调起支付的参数
            PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);

            //3.解析微信响应结果 封装Map对象
            if (response != null) {
                String timeStamp = response.getTimeStamp();
                String nonceStr = response.getNonceStr();
                String packageVal = response.getPackageVal();
                String signType = response.getSignType();
                String paySign = response.getPaySign();
                Map<String, String> map = new HashMap<>();
                map.put("timeStamp", timeStamp);
                map.put("nonceStr", nonceStr);
                map.put("package", packageVal);
                map.put("signType", signType);
                map.put("paySign", paySign);
                return map;
            }
            return null;
        } catch (GuiguException e) {
            log.error("[支付系统]生成本地交易记录且获取小程序拉取微信支付参数有误：{}", e);
            throw new RuntimeException(e);
        }
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 下单或充值微信支付成功后，收到来自微信支付结果通知
     *
     * @return {code:"SUCCESS",message:"成功"}
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public Map<String, String> wxPaySuccessNotify(HttpServletRequest request) {
        //1.商户验证签名，以确认请求来自微信，而不是其他的第三方,杜绝“假通知”出现;得到支付信息
        //1.1 从请求头中获取封装请求参数对象数据
        String signature = request.getHeader("Wechatpay-Signature");
        String serial = request.getHeader("Wechatpay-Serial");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String signaureType = request.getHeader("Wechatpay-Signature-Type");
        log.info("签名：{}，序列号：{}，随机数：{}，时间戳：{}，签名类型：{}", signature, serial, nonce, timestamp, signaureType);
        //1.2 获取请求体中所有业务数据(被加密后)
        String requestBody = PayUtil.readData(request);
        //1.3 构造 RequestParam请求参数对象
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .body(requestBody)
                .build();
        //1.4 初始化 NotificationParser 通知解析器对象
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        //1.5 验签、解密并转换成 Transaction
        Transaction transaction = parser.parse(requestParam, Transaction.class);

        //2.确保幂等性 采用redis提供String set k v ex nx 来实现
        //2.1 获取交易对象中订单编号
        String orderNo = transaction.getOutTradeNo();
        //2.2 尝试存入Redis
        String key = RedisConstant.BUSINESS_PREFIX + orderNo;
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 25, TimeUnit.HOURS);
        try {
            if (flag) {
                //3.TODO 先检查对应业务数据:支付状态需要已支付且用户付款金额跟商户侧金额是否一致
                Transaction.TradeStateEnum tradeState = transaction.getTradeState();
                if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
                    //3.1 说明用户支付成功，进一步判断商户侧金额跟微信交易对象中支付金额是否一致
                    Integer payerTotal = transaction.getAmount().getPayerTotal();
                    //3.2 付款金额是一致
                    if (payerTotal.intValue() == 1) {
                        //4.TODO 核心业务处理
                        log.info("[支付系统]收到来自微信异步回到，开始处理核心业务");
                        paymentInfoService.updatePaymentInfo(transaction);
                    }
                }
            }
        } catch (Exception e) {
            redisTemplate.delete(key);
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * 模拟 无权限支付，没有办法处理同步，异步回调
     * 解决：产生支付二维码，前段轮询查询订单支付状态，假设用户付款成功了在同步回调固定返回true,处理后续业务1.更新本地交易 2.订单业务 3.充值业务
     * @param orderNo
     * @return
     */
    //@Override
    //@GlobalTransactional(rollbackFor = Exception.class)
    //public Boolean queryPayStatus(String orderNo) {
    //    //1.伪造微信交易对象
    //    Transaction transaction = new Transaction();
    //    transaction.setOutTradeNo(orderNo);
    //    transaction.setTransactionId("wx"+ IdUtil.getSnowflakeNextId());
    //    //2.处理核心业务：本地交易记录及后续业务
    //    paymentInfoService.updatePaymentInfo(transaction);
    //    return true;
    //}

    /**
     * 根据商户端订单编号查询支付结果
     *
     * @param orderNo
     * @return
     */
    @Override
    public Boolean queryPayStatus(String orderNo) {
        //1.调用微信支付接口业务对象
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
        //2.根据商户侧订单编号查询交易对象
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(wxPayV3Config.getMerchantId());
        request.setOutTradeNo(orderNo);
        Transaction transaction = service.queryOrderByOutTradeNo(request);
        if (transaction != null) {
            Transaction.TradeStateEnum tradeState = transaction.getTradeState();
            //2.1 判断支付结果 判断支付金额微信端跟商户端金额是否一致
            if (Transaction.TradeStateEnum.SUCCESS == tradeState) {
                Integer payerTotal = transaction.getAmount().getPayerTotal();
                //TODO 根据本地交易交流中金额跟微信端用户实付金额比较
                if (1 == payerTotal) {
                    return true;
                }
            }
        }
        return false;
    }
}
